
// import oimo.collision.geometry.Aabb;
// import oimo.collision.geometry.ConvexGeometry;
// import oimo.collision.geometry.RayCastHit;
// import oimo.collision.narrowphase.detector.gjkepa.GjkEpa;
// import oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState;
// import oimo.common.Transform;
// import oimo.common.Vec3;
// import oimo.m.Vec3;
// import oimo.m.M;

import { Proxy } from "./Proxy";
import { ProxyPair } from "./ProxyPair";
import { Transform } from "../../common/Transform";
import { Vec3 } from "../../common/Vec3";
import { RayCastHit } from "../geometry/RayCastHit";
import { M } from "../../common/M";
import { Aabb } from "../geometry/Aabb";
import { ConvexGeometry } from "../geometry/ConvexGeometry";
import { GjkEpa } from "../narrowphase/detector/gjkepa/GjkEpa";
import { GjkEpaResultState } from "../narrowphase/detector/gjkepa/GjkEpaResultState";
import { BroadPhaseProxyCallback } from "./BroadPhaseProxyCallback";

/**
 * The abstract class of a broad-phase collision detection algorithm.
 */
export class BroadPhase {
	_type: number;
	_numProxies: number;
	_proxyList: Proxy;
	_proxyListLast: Proxy;

	_proxyPairList: ProxyPair;
	_incremental: boolean;

	_testCount: number;

	_proxyPairPool: ProxyPair;
	_idCount: number;

	_convexSweep: ConvexSweepGeometry;
	_aabb: AabbGeometry;

	identity: Transform;
	zero: Vec3;
	rayCastHit: RayCastHit;


	constructor(type: number) {
		this._type = type;

		this._numProxies = 0;
		this._proxyList = null;
		this._proxyListLast = null;

		this._proxyPairList = null;
		this._incremental = false;

		this._testCount = 0;

		this._proxyPairPool = null;
		this._idCount = 0;

		this._convexSweep = new ConvexSweepGeometry();
		this._aabb = new AabbGeometry();

		this.identity = new Transform();
		this.zero = new Vec3();
		this.rayCastHit = new RayCastHit();
	}

	// --- ---
	protected pickAndPushProxyPair(p1: Proxy, p2: Proxy): void {
		var pp: ProxyPair;
		if (this._proxyPairPool != null) {
			pp = this._proxyPairPool;
			this._proxyPairPool = this._proxyPairPool._next;
			pp._next = null;
		} else {
			pp = new ProxyPair();
		}

		if (this._proxyPairList == null) {
			this._proxyPairList = pp;
		} else {
			pp._next = this._proxyPairList;
			this._proxyPairList = pp;
		}

		pp._p1 = p1;
		pp._p2 = p2;
	}


	protected poolProxyPairs(): void {
		var p: ProxyPair = this._proxyPairList;
		if (p != null) {
			do {
				p._p1 = null;
				p._p2 = null;
				p = p._next;
			} while (p != null);
			this._proxyPairList._next = this._proxyPairPool;
			this._proxyPairPool = this._proxyPairList;
			this._proxyPairList = null;
		}
	}


	protected addProxy(p: Proxy): void {
		this._numProxies++;
		if (this._proxyList == null) {
			this._proxyList = p;
			this._proxyListLast = p;
		} else {
			this._proxyListLast._next = p;
			p._prev = this._proxyListLast;
			this._proxyListLast = p;
		}
	}


	protected removeProxy(p: Proxy): void {
		this._numProxies--;
		let prev = p._prev;
		let next = p._next;
		if (prev != null) {
			prev._next = next;
		}
		if (next != null) {
			next._prev = prev;
		}
		if (p = this._proxyList) {
			this._proxyList = next;
		}
		if (p == this._proxyListLast) {
			this._proxyListLast = prev;
		}
		p._prev = null;
		p._next = null;
	}

	private abs(x: number): number {
		return x < 0 ? -x : x;
	}
	private min(x: number, y: number): number {
		return x < y ? x : y;
	}
	private max(x: number, y: number): number {
		return x > y ? x : y;
	}

	protected aabbSegmentTest(aabbMin: Vec3, aabbMax: Vec3, begin: Vec3, end: Vec3): boolean {
		var x1: number = M.vec3_get(begin, 0);
		var y1: number = M.vec3_get(begin, 1);
		var z1: number = M.vec3_get(begin, 2);
		var x2: number = M.vec3_get(end, 0);
		var y2: number = M.vec3_get(end, 1);
		var z2: number = M.vec3_get(end, 2);
		var sminx: number = this.min(x1, x2);
		var sminy: number = this.min(y1, y2);
		var sminz: number = this.min(z1, z2);
		var smaxx: number = this.max(x1, x2);
		var smaxy: number = this.max(y1, y2);
		var smaxz: number = this.max(z1, z2);
		var pminx: number = M.vec3_get(aabbMin, 0);
		var pminy: number = M.vec3_get(aabbMin, 1);
		var pminz: number = M.vec3_get(aabbMin, 2);
		var pmaxx: number = M.vec3_get(aabbMax, 0);
		var pmaxy: number = M.vec3_get(aabbMax, 1);
		var pmaxz: number = M.vec3_get(aabbMax, 2);

		if (
			// axis1: (1, 0, 0)
			// axis2: (0, 1, 0)
			// axis3: (0, 0, 1)
			pminx > smaxx || pmaxx < sminx ||
			pminy > smaxy || pmaxy < sminy ||
			pminz > smaxz || pmaxz < sminz
		) {
			return false;
		}

		var dx: number = x2 - x1;
		var dy: number = y2 - y1;
		var dz: number = z2 - z1;
		var adx: number = this.abs(dx);
		var ady: number = this.abs(dy);
		var adz: number = this.abs(dz);
		var pextx: number = (pmaxx - pminx) * 0.5;
		var pexty: number = (pmaxy - pminy) * 0.5;
		var pextz: number = (pmaxz - pminz) * 0.5;
		var pcntx: number = (pmaxx + pminx) * 0.5;
		var pcnty: number = (pmaxy + pminy) * 0.5;
		var pcntz: number = (pmaxz + pminz) * 0.5;
		var cpx: number = x1 - pcntx;
		var cpy: number = y1 - pcnty;
		var cpz: number = z1 - pcntz;

		if (
			// axis4: (dx, dy, dz) x (1, 0, 0) = (0, dz, -dy)
			// axis5: (dx, dy, dz) x (0, 1, 0) = (-dz, 0, dx)
			// axis6: (dx, dy, dz) x (0, 0, 1) = (dy, -dx, 0)
			this.abs(cpy * dz - cpz * dy) - (pexty * adz + pextz * ady) > 0 ||
			this.abs(cpz * dx - cpx * dz) - (pextz * adx + pextx * adz) > 0 ||
			this.abs(cpx * dy - cpy * dx) - (pextx * ady + pexty * adx) > 0
		) {
			return false;
		}

		return true;
	}


	protected aabbConvexSweepTest(aabbMin: Vec3, aabbMax: Vec3, convex: ConvexGeometry, begin: Transform, translation: Vec3): boolean {
		this._aabb.min.copyFrom(aabbMin);
		this._aabb.max.copyFrom(aabbMax);
		this._convexSweep.init(convex, begin, translation);
		var gjkEpa: GjkEpa = GjkEpa.getInstance();
		if (gjkEpa.computeDistance(this._convexSweep, this._aabb, begin, this.identity, null) == GjkEpaResultState._SUCCEEDED) {
			return gjkEpa.distance <= 0;
		}
		return false;
	}

	// --- public ---

	/**
	 * Returns a new proxy connected with the user data `userData` containing the axis-aligned
	 * bounding box `aabb`, and adds the proxy into the collision space.
	 */
	public createProxy(userData: any, aabb: Aabb): Proxy {
		return null;
	}

	/**
	 * Removes the proxy `proxy` from the collision space.
	 */
	public destroyProxy(proxy: Proxy): void {
	}

	/**
	 * Moves the proxy `proxy` to the axis-aligned bounding box `aabb`. `displacement` is the
	 * difference between current and previous center of the AABB. This is used for predicting
	 * movement of the proxy.
	 */
	public moveProxy(proxy: Proxy, aabb: Aabb, displacement: Vec3): void {
	}

	/**
	 * Returns whether the pair of `proxy1` and `proxy2` is overlapping. As proxies can be larger
	 * than the containing AABBs, two proxies may overlap even though their inner AABBs are separate.
	 */
	public isOverlapping(proxy1: Proxy, proxy2: Proxy): boolean {
		return M.aabb_overlap(proxy1._aabbMin, proxy1._aabbMax, proxy2._aabbMin, proxy2._aabbMax);
	}

	/**
	 * Collects overlapping pairs of the proxies and put them into a linked list. The linked list
	 * can be get through `BroadPhase.getProxyPairList` method.
	 *
	 * Note that in order to collect pairs, the broad-phase algorithm requires to be informed of
	 * movements of proxies through `BroadPhase.moveProxy` method.
	 */
	public collectPairs(): void {
	}

	/**
	 * Returns the linked list of collected pairs of proxies.
	 */
	public getProxyPairList(): ProxyPair {
		return this._proxyPairList;
	}

	/**
	 * Returns whether to collect only pairs created in the last step. If this returns
	 * true, the pairs that are not collected might still be overlapping. Otherwise, such
	 * pairs are guaranteed to be separated.
	 */
	public isIncremental(): boolean {
		return this._incremental;
	}

	/**
	 * Returns the number of broad-phase AABB tests.
	 */
	public getTestCount(): number {
		return this._testCount;
	}

	/**
	 * Performs a ray casting. `callback.process` is called for all proxies the line segment
	 * from `begin` to `end` intersects.
	 */
	public rayCast(begin: Vec3, end: Vec3, callback: BroadPhaseProxyCallback): void {
	}

	/**
	 * Performs a convex casting. `callback.process` is called for all shapes the convex geometry
	 * `convex` hits. The convex geometry translates by `translation` starting from the beginning
	 * transform `begin`.
	 */
	public convexCast(convex: ConvexGeometry, begin: Transform, translation: Vec3, callback: BroadPhaseProxyCallback): void {
	}

	/**
	 * Performs an AABB query. `callback.process` is called for all proxies that their AABB
	 * and `aabb` intersect.
	 */
	public aabbTest(aabb: Aabb, callback: BroadPhaseProxyCallback): void {
	}
}


class ConvexSweepGeometry extends ConvexGeometry {
	c: ConvexGeometry;
	localTranslation: Vec3;

	constructor() {
		super(-1);
	}

	public init(c: ConvexGeometry, transform: Transform, translation: Vec3): void {
		this.c = c;

		this.localTranslation = translation.mulMat3(transform._rotation);

		this._gjkMargin = c._gjkMargin;
	}

	public computeLocalSupportingVertex(dir: Vec3, out: Vec3): void {
		this.c.computeLocalSupportingVertex(dir, out);
		if (dir.dot(this.localTranslation) > 0) {
			out.addEq(this.localTranslation);
		}
	}
}

class AabbGeometry extends ConvexGeometry {
	min: Vec3;
	max: Vec3;

	constructor() {
		super(-1);
		this.min = new Vec3();
		this.max = new Vec3();
	}

	public computeLocalSupportingVertex(dir: Vec3, out: Vec3): void {
		out.x = dir.x > 0 ? this.max.x : this.min.x;
		out.y = dir.y > 0 ? this.max.y : this.min.y;
		out.z = dir.z > 0 ? this.max.z : this.min.z;
	}
}
